// 12.2 动态数组
/**
 * new和delete运算符一次分配/释放一个对象，但某些应用需要一次为很多对象分配内存的功能。
 * 为了支持这种需求，C++语言和标准库提供了两种一次分配一个对象数组的方法。C++语言定义了另一种new表达式语法，可以分配并初始化一个对象数组。
 * 很多（可能是大多数）应用都没有直接访问动态数组的需求。
 * 大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

int main()
{
    // 12.2.1 new和数组
    // 为了让new分配一个对象数组，我们要在类型名之后跟一对方括号，在其中指明要分配的对象的数目。
    // 在下例中，new分配要求数量的对象并（假定分配成功后）返回指向第一个对象的指针：[插图]
    int *pia = new int[10]; // 方括号中的大小必须是整型，但不必是常量。pia指向第一个int
    // 也可以用一个表示数组类型的类型别名（参见2.5.1节，第60页）来分配一个数组，这样，new表达式中就不需要方括号了：[插图]
    typedef int arrT[42]; // arrT表示42个int型数组
    int *p = new arrT;    // 分配一个42个int的数组，p指向第一个int

    // 分配一个数组会得到一个元素类型的指针
    // 虽然我们通常称new T[]分配的内存为“动态数组”，但这种叫法某种程度上有些误导。
    // 当用new分配一个数组时，我们并未得到一个数组类型的对象，而是得到一个数组元素类型的指针。
    // 由于分配的内存并不是一个数组类型，因此不能对动态数组调用begin或end（参见3.5.3节，第106页）。
    // 要记住我们所说的动态数组并不是数组类型，这是很重要的。

    // 初始化动态分配对象的数组
    // 可以对数组中的元素进行值初始化（参见3.3.1节，第88页），方法是在大小之后跟一对空括号。[插图]
    int *pia1 = new int[10];         // 10个未初始化的int
    int *pia2 = new int[10]();       // 10个值初始化的int
    string *psa1 = new string[10];   // 10个空string
    string *psa2 = new string[10](); // 10个空string
    // 在新标准中，我们还可以提供一个元素初始化器的花括号列表
    int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};           // 10个int分别用列表中对应的初始化器进行初始化
    string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')}; // 前4个用给定的初始化器进行初始化，剩余的进行值初始化
    // 如果初始化器数目大于元素数目，则new表达式失败，不会分配任何内存。
    // 在本例中，new会抛出一个类型为bad_array_new_length的异常。类似bad_alloc，此类型定义在头文件new中。

    // 动态分配一个空数组是合法的
    // 虽然我们不能创建一个大小为0的静态数组对象，但当n等于0时，调用new[n]是合法的：[插图]
    char arr[0];            // 错误，不能定义长度为0的数组
    char *cp = new char[0]; // 正确，但cp不能解引用

    // 释放动态数组
    // 为了释放动态数组，我们使用一种特殊形式的delete——在指针前加上一个空方括号对：[插图]
    int i = 0, *p = new int(i);
    delete p;      // p必须指向一个动态分配的对象或为空
    delete[] pia3; // pia3必须指向一个动态分配的数组或为空
    // 第二条语句销毁pia3指向的数组中的元素，并释放对应的内存。
    // 数组中的元素按逆序销毁，即，最后一个元素首先被销毁，然后是倒数第二个，依此类推。
    // 当我们释放一个指向数组的指针时，空方括号对是必需的：它指示编译器此指针指向一个对象数组的第一个元素。
    // 如果我们在delete一个数组指针时忘记了方括号，或者在delete一个单一对象的指针时使用了方括号，编译器很可能不会给出警告。
    // 我们的程序可能在执行过程中在没有任何警告的情况下行为异常。

    // 智能指针和动态数组
    // 标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组，我们必须在对象类型后面跟一对空方括号：[插图]
    unique_ptr<int[]> up(new int[10]); // up指向一个包含10个未初始化int的数组
    up.release();                      // 自动用delete []销毁其指针
    // 当一个unique_ptr指向一个数组时，我们可以使用下标运算符来访问数组中的元素：[插图]
    for (size_t i = 0; i != 10; ++i)
    {
        up[i] = i; // 为每个元素赋予一个新值
    }
    // 与unique_ptr不同，shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理一个动态数组，必须提供自己定义的删除器：[插图]
    shared_ptr<int> sp(new int[10], [](int *p)
                       { delete[] p; }); // 为了使用shared_ptr管理动态数组，必须提供一个删除器
    sp.reset();                          // 使用我们提供的lambda释放数组，它使用delete[]
    // shared_ptr不直接支持动态数组管理这一特性会影响我们如何访问数组中的元素：[插图]
    for (size_t i = 0; i != 10; ++i)
    {
        *(sp.get() + i) = i; // 使用get获取一个内置指针
    }

    return 0;
}